﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using System.ComponentModel;

namespace EzDesk.EventAggregation
{
    /// <summary>
    /// Specifies on which thread a <see cref="CompositePresentationEvent{TPayload}"/> subscriber will be called.
    /// </summary>
    public enum ThreadOption
    {
        /// <summary>
        /// The call is done on the same thread on which the <see cref="CompositePresentationEvent{TPayload}"/> was published.
        /// </summary>
        PublisherThread,

        /// <summary>
        /// The call is done on the UI thread.
        /// </summary>
        UIThread,

        /// <summary>
        /// The call is done asynchronously on a background thread.
        /// </summary>
        BackgroundThread
    }

    public class EventSubscriptor<TPayload>:IDisposable
    {
        public CompositeEvent<TPayload> EventAggregator
        { get; set; }
        public Action<TPayload> Method
        { get; set; }
        public ThreadOption ThreadOption { get; set; }
        public void Execute(TPayload data)
        {
            ThreadExecution.Execute<TPayload>(Method, data, ThreadOption);
        }

        #region IDisposable Members
        private bool m_disposed;

        ~EventSubscriptor()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!m_disposed)
            {
                if (disposing)
                {
                    EventAggregator.Unsubscribe(this);
                    this.EventAggregator = null;
                    this.Method = null;
                }
                m_disposed = true;
            }
        }

        #endregion
    }
    public class ThreadExecution
    {
        public static void Execute<T>(Action<T> method, T data, ThreadOption threadOption)
        {
            switch (threadOption)
            {
                case ThreadOption.PublisherThread:
                    CommonExecute(method, data);
                    break;
                case ThreadOption.BackgroundThread:
                    BackgroundExecute(method, data);
                    break;
                case ThreadOption.UIThread:
                    UIThreadExecute(method,data);
                    break;
            }
        }

        public static void CommonExecute<T>(Action<T> method, T data)
        {
            method(data);
        }
        public static void BackgroundExecute<T>(Action<T> method, T data)
        {
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += (sender, e) => method((T)e.Argument);

            // handle worker.RunWorkerCompleted and log exceptions?
            worker.RunWorkerAsync(data);
        }
        public static void UIThreadExecute<T>(Action<T> method, T data)
        {
            if (Application.Current != null)
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method, data);
            }
            else
            {
                CommonExecute(method,data);
            }
        }
    }

    
    public class CompositeEvent<TPayload> :EventBase
    {

        private readonly List<EventSubscriptor<TPayload>> _subscriptions = new List<EventSubscriptor<TPayload>>();

        /// <summary>
        /// Gets the list of current subscriptions.
        /// </summary>
        /// <value>The current subscribers.</value>
        protected ICollection<EventSubscriptor<TPayload>> Subscriptions
        {
            get { return _subscriptions; }
        }

        public virtual void Publish(TPayload payload)
        {
            foreach (var subscribe in Subscriptions.ToList())
            {
                subscribe.Execute(payload);
            }
        }


        public EventSubscriptor<TPayload> Subscribe(Action<TPayload> method)
        {
            var subscribe = new EventSubscriptor<TPayload>
            {
                Method = method,
                ThreadOption = ThreadOption.UIThread,
                EventAggregator = this
            };
            lock (Subscriptions)
            {
                _subscriptions.Add(subscribe);
            }
            return subscribe;
        }


        public EventSubscriptor<TPayload> Subscribe(Action<TPayload> method, ThreadOption threadOption)
        {
            var subscribe = new EventSubscriptor<TPayload>
            {
                Method = method,
                ThreadOption = threadOption,
                EventAggregator = this
               
            };
            lock (Subscriptions)
            {
                _subscriptions.Add(subscribe);
            }
            return subscribe;
        }

        public void Unsubscribe(EventSubscriptor<TPayload> subscribe)
        {
            lock (Subscriptions)
            {
                 _subscriptions.Remove(subscribe);
            }
        }

        public void Unsubscribe(Action<TPayload> method)
        {
            lock (Subscriptions)
            {
                var subscribe = _subscriptions.FirstOrDefault(sub => sub.Method == method);
                _subscriptions.Remove(subscribe);
            }
        }
    }

    public class EventAggregator
    {
        private Dictionary<string, object> _eventDictionary = new Dictionary<string, object>();
        public TEvent GetEvent<TEvent>() where TEvent :new()
        {
            string typeString = typeof (TEvent).ToString();
           if(!_eventDictionary.ContainsKey(typeString))
           {
               _eventDictionary[typeString] = new TEvent();
           }

            return (TEvent)_eventDictionary[typeString];
        }
    }


    public static class EventAggregator<TEvent> where TEvent : new()
    {
        private static object _objLock = new object();
        private static TEvent _instance;

        private static TEvent Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_objLock)
                    {
                        if (_instance == null)
                        {
                            _instance = new TEvent();
                        }
                    }
                }
                return _instance;
            }
        }

        public static TEvent GetEvent()
        {
            if (_instance == null)
            {
                lock (_objLock)
                {
                    if (_instance == null)
                    {
                        _instance = new TEvent();
                    }
                }
            }
            return _instance;
        }
    }
}
